package com.xzzz.irda.guardcat.server.metric;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import com.xzzz.common.base.exception.XzException500;
import com.xzzz.common.base.pojo.R;
import com.xzzz.common.base.util.DateUtils;
import com.xzzz.irda.guardcat.core.util.GuardcatUtil;
import com.xzzz.irda.guardcat.server.app.pojo.MachineInfo;
import com.xzzz.irda.guardcat.server.metric.gc.GcInfo;
import com.xzzz.irda.guardcat.server.metric.memory.JmmInfo;
import com.xzzz.irda.guardcat.server.metric.physical.PhysicalInfo;
import com.xzzz.irda.guardcat.server.metric.thread.ThreadInfo;
import com.xzzz.irda.guardcat.server.repos.redis.PhysicalRepositoryInRedis;
import com.xzzz.irda.guardcat.server.metric.snapshot.SnapshotRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 【Server】机器内存信息
 *
 * @author xzzz
 */
@Slf4j
@RequestMapping("/guardcat")
@RestController("guardcatMetricController")
public class GuardcatMetricController {
    @Resource
    @Qualifier(value = "jmmRepository")
    private MetricRepository<JmmInfo> memory;
    @Resource
    @Qualifier(value = "gcRepository")
    private MetricRepository<GcInfo> gc;
    @Resource
    @Qualifier(value = "threadRepository")
    public MetricRepository<ThreadInfo> thread;
    @Autowired
    private PhysicalRepositoryInRedis physicalRedis;
    @Autowired
    public SnapshotRepository snapshot;

    /**
     * 机器内存信息
     *
     * @param machineId 机器ID
     * @return 内存信息
     */
    @GetMapping("/jmm")
    public R<JmmInfo> getMemory(String machineId) {
        return R.ok(memory.getByMachineId(machineId));
    }

    /**
     * 机器内存直方图
     *
     * @param machineId 机器ID
     * @param useField  某个内容信息
     * @return 内存直方图
     */
    @GetMapping("/jmm/echart/line")
    public R<Map<String, Object>> getMemoryList(String machineId, String useField) {
        Map<String, Object> param = new HashMap<>(2);
        if (StrUtil.isBlank(useField)) {
            useField = "hu";
        }
        param.put("use", useField);
        MachineInfo machineInfo = new MachineInfo();
        machineInfo.setMachineId(machineId);

        List<JmmInfo> list = memory.list(machineInfo, null, param);
        Object[] xLineTime = new Object[list.size()];
        // 使用的内存统计
        Object[] use = new Object[list.size()];
        Object[] ou = new Object[list.size()];
        Object[] eu = new Object[list.size()];
        Object[] su = new Object[list.size()];
        String metricName = "";

        for (int i = 0; i < list.size(); i++) {
            xLineTime[i] = list.get(list.size() - 1 - i).getCreTime().substring(5);
            JmmInfo memory = list.get(list.size() - 1 - i);

            switch (useField) {
                case "hu":
                    metricName = "堆内存统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getHeapUsage().getCommittedValue());
                    ou[i] = GuardcatUtil.byteToMB(memory.getOldGen().getUsedValue());
                    eu[i] = GuardcatUtil.byteToMB(memory.getEdenSpace().getUsedValue());
//                    su[i] = GuardcatUtil.byteToMB(memory.getSurvivorSpace().getUsedValue());
                    break;
                case "ou":
                    metricName = "老年代 Old 区统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getOldGen().getUsedValue());
                    break;
                case "eu":
                    metricName = "新生代 Eden 区统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getEdenSpace().getUsedValue());
                    break;
                case "su":
                    metricName = "幸存代 Survivor 区统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getSurvivorSpace().getUsedValue());
                    break;
                case "nhu":
                    metricName = "非堆内存统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getNonHeapUsage().getUsedValue());
                    break;
                case "mu":
                    metricName = "元空间内存统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getMetaspaceUsage().getUsedValue());
                    break;
                case "ccu":
                    metricName = "JIT 编译代码缓存区内存统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getCodeCacheUsage().getUsedValue() == null ? 0 : memory.getCodeCacheUsage().getUsedValue());
                    break;
                case "ccsu":
                    metricName = "类压缩数据区统计";
                    use[i] = GuardcatUtil.byteToMB(memory.getCompressedClassSpace().getUsedValue() == null ? 0 : memory.getCompressedClassSpace().getUsedValue());
                    break;
            }
        }

        Map<String, Object> map = new HashMap<>(3);
        map.put("x", xLineTime);
        map.put("use", use);
        map.put("ou", ou);
        map.put("eu", eu);
        map.put("su", su);
        map.put("metricName", metricName);

        return R.ok(map);
    }

    /**
     * 删除过期内存信息
     */
    @DeleteMapping("/jmm")
    public R<?> expireMemory() {
        memory.expire();
        return R.ok();
    }

    /**
     * 机器物理信息
     *
     * @param machineId 机器ID
     * @return 机器物理信息
     */
    @GetMapping("/physical")
    public R<PhysicalInfo> getPhysical(String machineId) {
        return R.ok(physicalRedis.getByMachineId(machineId));
    }

    /**
     * 机器GC信息
     *
     * @param machineId 机器ID
     * @return 机器GC信息
     */
    @GetMapping("/gc")
    public R<Map<String, GcInfo>> getGc(String machineId) {
        Map<String, GcInfo> map = new HashMap<>(2);

        MachineInfo machineInfo = new MachineInfo();
        machineInfo.setMachineId(machineId);
        Map<String, Object> args = new HashMap<>();
        args.put("last", "limit 1");

        GcInfo whereYoung = new GcInfo();
        whereYoung.setName("PS Scavenge");
        List<GcInfo> young = gc.list(machineInfo, whereYoung, args);
        if (CollUtil.isNotEmpty(young)) {
            young.forEach(gc -> {
                if ("PS Scavenge".equals(gc.getName())) {
                    map.put("young", gc);
                }
            });
        }

        GcInfo whereFull = new GcInfo();
        whereYoung.setName("PS MarkSweep");
        List<GcInfo> full = gc.list(machineInfo, whereFull, args);
        if (CollUtil.isNotEmpty(full)) {
            full.forEach(gc -> {
                if ("PS MarkSweep".equals(gc.getName())) {
                    map.put("full", gc);
                }
            });
        }


        return R.ok(map);
    }

    /**
     * 机器GC折线图
     *
     * @param machineId 机器信息
     * @return 机器GC直方图
     */
    @GetMapping("/gc/echart/line")
    public R getGcList(String machineId) {
        List<GcInfo> gcs = gc.listByMachineId(machineId);
        Map<String, Object[]> map = new HashMap<>(3);


        int length = gcs.stream().collect(Collectors.groupingBy(GcInfo::getStartTime)).size();
        Map<String, Object> keys = new HashMap<>(length);

        Object[] x = new Object[length];
        Object[] y = new Object[length];
        Object[] f = new Object[length];

        for (int i = 0; i < gcs.size(); i++) {
            keys.put(gcs.get(gcs.size() - 1 - i).getStartTime().substring(5), null);
            x[keys.size() - 1] = gcs.get(gcs.size() - 1 - i).getStartTime().substring(5);
            // PS Scavenge  : young gc;
            // PS MarkSweep : full  gc
            if ("PS Scavenge".equals(gcs.get(gcs.size() - 1 - i).getName())) {
                y[keys.size() - 1] = gcs.get(gcs.size() - 1 - i).getDuration();
            } else {
                f[keys.size() - 1] = gcs.get(gcs.size() - 1 - i).getDuration();
            }
        }

        map.put("x", x);
        map.put("y", y);
        map.put("f", f);
        return R.ok(map);
    }

    /**
     * 线程信息折线图
     *
     * @param machineId 机器信息
     * @return 机器GC直方图
     */
    @GetMapping("/thread/echart/line")
    public R<Map<String, Object>> getThreadList(String machineId) {
        List<ThreadInfo> list = thread.listByMachineId(machineId);

        Object[] x = new Object[list.size()];  // thread count
        Object[] tc = new Object[list.size()];  // thread count
        Object[] ptc = new Object[list.size()]; // peak
        Object[] tstc = new Object[list.size()];// total started
        Object[] dtc = new Object[list.size()]; // daemon

        for (int i = list.size() - 1, j = 0; i >= 0; i--, j++) {
//        for (int i = list.size() - 1; i >= 0; i--) {
            tc[j] = list.get(i).getThreadCount();
            ptc[j] = list.get(i).getPeakThreadCount();
            tstc[j] = list.get(i).getTotalStartedThreadCount();
            dtc[j] = list.get(i).getDaemonThreadCount();
            x[j] = DateUtils.toYMDHMS(list.get(i).getCreTime()).substring(5);
        }

        Map<String, Object> result = new HashMap<>();
        result.put("x", x);
        result.put("tc", tc);
        result.put("ptc", ptc);
        result.put("tstc", tstc);
        result.put("dtc", dtc);
        return R.ok(result);
    }

    /**
     * 快照下载
     *
     * @param type      快照类型: objectStat;threadList;threadDump;deadLock;
     * @param appName   应用名称
     * @param machineId 机器ID
     * @param response  响应
     * @apiNote 响应体为快照文本
     */
    @GetMapping("/download")
    public void download(String type, String appName, String machineId, HttpServletResponse response) {
        String resp;
        if (SnapshotRepository.OBJECT_STAT.equals(type)) {
            resp = snapshot.objectStat(appName, machineId);
        } else if (SnapshotRepository.THREAD_LIST.equals(type)) {
            resp = snapshot.threadList(appName, machineId);
        } else if (SnapshotRepository.THREAD_DUMP.equals(type)) {
            resp = snapshot.threadDump(appName, machineId);
        } else if (SnapshotRepository.DEAD_LOCK.equals(type)) {
            resp = snapshot.deadLock(appName, machineId);
        } else {
            throw new XzException500(String.format("错误的快照类型: %s", type));
        }

        log.info("快照内容{}", resp);

        if (StrUtil.isBlank(resp)) {
            throw new XzException500(String.format("未查询到 [%s.%s] 的 [%s] 快照信息", appName, machineId, type));
        }

        try (InputStream is = new ByteArrayInputStream(resp.getBytes(StandardCharsets.UTF_8));
             BufferedInputStream bis = new BufferedInputStream(is);
             OutputStream os = response.getOutputStream();) {
            // 设置强制下载不打开
            response.setContentType("application/force-download");
            String fileName = String.format("%s_%s_%s", appName, type, DateUtil.now().replace(" ", "T"));
            // 设置文件名
            response.addHeader("Content-Disposition", "attachment;fileName=" + fileName + ".txt");
            byte[] buffer = new byte[1024];
            int i = bis.read(buffer);
            while (i != -1) {
                os.write(buffer, 0, i);
                i = bis.read(buffer);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
